﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Unis.Framework.Core.Exceptions;
using Unis.Framework.Core.Utility;
using Unis.Framework.ORM.Attributes;
using Unis.Framework.ORM.BaseDefiniens.Enums;
using Unis.ORM.Mapping;

namespace Unis.Framework.ORM.Mapping
{
    public sealed class AttMappingManager
    {
        private static AttMappingManager _Instance;
        private static object _Obj = new object();
        private Dictionary<Type, ModelMappingInfo> metaDataCache = new Dictionary<Type, ModelMappingInfo>();

        protected AttMappingManager()
        {
        }

        public bool CheckExistsModelMapping(Type t)
        {
            ModelMapAttribute customAttribute = Attribute.GetCustomAttribute(t, typeof(ModelMapAttribute)) as ModelMapAttribute;
            return (customAttribute != null);
        }

        public ModelMapAttribute GetModelMapAttribute(Type t)
        {
            return Attribute.GetCustomAttribute(t, typeof(ModelMapAttribute)) as ModelMapAttribute;
        }

        private List<string> getColumnsExceptKey(Type entityType)
        {
            Dictionary<string, FieldPropertyInfo> primaryKeys = this.GetModelMappingInfo(entityType).PrimaryKeys;
            entityType.GetProperties();
            List<string> list = this.getDbColumns(entityType);
            foreach (FieldPropertyInfo info in primaryKeys.Values)
            {
                list.Remove(info.FieldName);
            }
            return list;
        }

        private List<string> getDbColumns(Type entityType)
        {
            PropertyInfo[] properties = entityType.GetProperties();
            List<string> list = new List<string>();
            foreach (PropertyInfo info in properties)
            {
                ColumnMapAttribute customAttribute = Attribute.GetCustomAttribute(info, typeof(ColumnMapAttribute)) as ColumnMapAttribute;
                if (customAttribute != null)
                {
                    list.Add(customAttribute.ColumnName);
                }
            }
            return list;
        }

        public List<FieldPropertyInfo> GetEntityMappingPropertys(Type t)
        {
            PropertyInfo[] properties = t.GetProperties();
            List<FieldPropertyInfo> list = new List<FieldPropertyInfo>();
            foreach (PropertyInfo info in properties)
            {
                if (!info.IsSpecialName)
                {
                    FieldPropertyInfo item = new FieldPropertyInfo
                    {
                        FieldName = info.Name,
                        PropertyName = info.Name
                    };
                    list.Add(item);
                }
            }
            return list;
        }

        private Dictionary<string, FieldPropertyInfo> getKeyColumns(Type entityType, ModelMappingInfo m)
        {
            ModelMapAttribute customAttribute = Attribute.GetCustomAttribute(entityType, typeof(ModelMapAttribute)) as ModelMapAttribute;
            if (customAttribute == null)
            {
                throw new APPException(string.Format("在获取实体列时 类型{0} 没有配置 ModelMapAttribute 特性,请先配置", entityType.FullName));
            }
            string[] primaryKeys = customAttribute.PrimaryKeys;
            Dictionary<string, FieldPropertyInfo> dictionary = new Dictionary<string, FieldPropertyInfo>(primaryKeys.Length);
            for (int i = 0; i < primaryKeys.Length; i++)
            {
                string key = primaryKeys[i];
                if (!m.FieldPropertys.ContainsKey(key))
                {
                    throw new APPException(string.Format("指定的键值{0} 在实体类型{1}中不存在,或者存在但没有配置属性的MB.Orm.Mapping.Att.ColumnMap 特殊", key, entityType.FullName));
                }
                dictionary.Add(key, m.FieldPropertys[key]);
            }
            return dictionary;
        }

        public ModelMappingInfo GetModelMappingInfo(Type t)
        {
            lock (_Instance)
            {
                if (!this.metaDataCache.ContainsKey(t))
                {
                    this.metaDataCache.Add(t, this.parseObjectMappingInfo(t));
                }
            }
            return this.metaDataCache[t];
        }

        public string GetPrimaryKey(object entity, ref ModelMappingInfo entityMappingInfo)
        {
            Type t = entity.GetType();
            ModelMappingInfo modelMappingInfo = Instance.GetModelMappingInfo(t);
            if (string.IsNullOrEmpty(modelMappingInfo.MapTable))
            {
                throw new APPException(string.Format("数据实体{0} 配置有误，没有指定映射到的表。", t.FullName), APPMessageType.SysErrInfo);
            }
            if ((modelMappingInfo.PrimaryKeys == null) || (modelMappingInfo.PrimaryKeys.Count != 1))
            {
                throw new APPException(string.Format("数据实体{0} 配置有误，没有指定主键或者不是单一主键配置。", t.FullName), APPMessageType.SysErrInfo);
            }
            FieldPropertyInfo info2 = modelMappingInfo.PrimaryKeys.Values.FirstOrDefault<FieldPropertyInfo>();
            if ((info2 == null) || !Reflection.CheckObjectExistsProperty(entity, info2.FieldName))
            {
                throw new APPException(string.Format("在检查下级对象引用时,对象主键{0}在对象{1}中不存在。请检查配置信息！", info2.FieldName, entity.GetType().FullName), APPMessageType.DisplayToUser);
            }
            entityMappingInfo = modelMappingInfo;
            return info2.FieldName;
        }

        private ModelMappingInfo parseMetaData(Type t, ModelMappingInfo m)
        {
            PropertyInfo[] properties = t.GetProperties();
            m.FieldPropertys = new Dictionary<string, FieldPropertyInfo>(properties.Length);
            foreach (PropertyInfo info in properties)
            {
                ExclusiveAttribute customAttribute = Attribute.GetCustomAttribute(info, typeof(ExclusiveAttribute)) as ExclusiveAttribute;
                if (object.Equals(customAttribute, null))
                {
                    ColumnMapAttribute attribute2 = Attribute.GetCustomAttribute(info, typeof(ColumnMapAttribute)) as ColumnMapAttribute;
                    if (attribute2 != null)
                    {
                        FieldPropertyInfo info2 = new FieldPropertyInfo
                        {
                            PropertyInfo = info,
                            PropertyName = info.Name,
                            DeclaringType = info.DeclaringType,
                            FieldName = attribute2.ColumnName,
                            DbType = attribute2.DbType,
                            DefaultValue = attribute2.DefaultValue
                        };
                        AutoIncreaseAttribute objB = Attribute.GetCustomAttribute(info, typeof(AutoIncreaseAttribute)) as AutoIncreaseAttribute;
                        if (!object.Equals(null, objB))
                        {
                            info2.AutoIncrease = m.HasAutoIncreasePorperty = true;
                            info2.Step = objB.Step;
                            m.AutoIncreasePorperty = info2.PropertyName;
                        }
                        m.FieldPropertys.Add(info2.PropertyName, info2);
                    }
                }
            }
            m.PrimaryKeys = this.getKeyColumns(t, m);
            if (m.PrimaryKeys.Count > 1)
            {
                m.IsMultiPrimaryKey = true;
                return m;
            }
            m.IsMultiPrimaryKey = false;
            return m;
        }

        private ModelMappingInfo parseModelMapping(Type entityType, ModelMappingInfo m)
        {
            ModelMapAttribute customAttribute = Attribute.GetCustomAttribute(entityType, typeof(ModelMapAttribute)) as ModelMapAttribute;
            if (customAttribute != null)
            {
                m.MapTable = customAttribute.TableName;
                m.XmlConfigFileName = customAttribute.XmlFileName;
                m.ConfigOptions = customAttribute.ConfigOptions;
                return m;
            }
            m.MapTable = entityType.Name;
            m.XmlConfigFileName = entityType.Name;
            m.ConfigOptions = ModelConfigOptions.CreateSqlByXmlCfg | ModelConfigOptions.ColumnCfgByAttribute;
            return m;
        }

        private ModelMappingInfo parseObjectMappingInfo(Type t)
        {
            ModelMappingInfo m = new ModelMappingInfo
            {
                ClassName = t.Name,
                EntityType = t
            };
            this.parseModelMapping(t, m);
            this.parseMetaData(t, m);
            return m;
        }

        // Properties
        public static AttMappingManager Instance
        {
            get
            {
                if (_Instance == null)
                {
                    lock (_Obj)
                    {
                        if (_Instance == null)
                        {
                            _Instance = new AttMappingManager();
                        }
                    }
                }
                return _Instance;
            }
        }
    }
}
